#include <cstdlib>
#include <cmath>

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

// #include "objLoader.hpp"
#include "car.hpp"
#include "ground.hpp"
#include "input.hpp"

Car car = Car();
Ground ground = Ground();

InputManager inputManagerX = InputManager(1e-1);
InputManager inputManagerY = InputManager(1e-2);

float cameraX=0.0f, cameraZ=0.0f;

/***************************************************************************************
*    Title:        GLUT-Tutorial
*    Author:       Lighthouse3d
*    Date:         26 Apr 2015
*    Code version: 36a9ccd2ffef4812eaa75424652c1417e6ebff8e
*    Availability: https://github.com/lighthouse3d/GLUT-Tutorial
*
***************************************************************************************/

int h, w;

void *font = GLUT_STROKE_ROMAN;

// the key states. These variables will be zero
//when no key is being presses
float deltaAngle = 0.0f;
int xOrigin = -1;

void renderBitmapString(
	float x,
	float y,
	float z,
	void *font,
	char *string)
{

	char *c;
	glRasterPos3f(x, y, z);
	for (c = string; *c != '\0'; c++)
	{
		glutBitmapCharacter(font, *c);
	}
}

void restorePerspectiveProjection()
{

	glMatrixMode(GL_PROJECTION);
	// restore previous projection matrix
	glPopMatrix();

	// get back to modelview mode
	glMatrixMode(GL_MODELVIEW);
}

void setOrthographicProjection()
{

	// switch to projection mode
	glMatrixMode(GL_PROJECTION);

	// save previous matrix which contains the
	//settings for the perspective projection
	glPushMatrix();

	// reset matrix
	glLoadIdentity();

	// set a 2D orthographic projection
	gluOrtho2D(0, w, h, 0);

	// switch back to modelview mode
	glMatrixMode(GL_MODELVIEW);
}

void changeSize(int ww, int hh)
{

	h = hh;
	w = ww;
	// Prevent a divide by zero, when window is too short
	// (you cant make a window of zero width).
	if (h == 0)
		h = 1;

	float ratio = w * 1.0 / h;

	// Use the Projection Matrix
	glMatrixMode(GL_PROJECTION);

	// Reset Matrix
	glLoadIdentity();

	// Set the viewport to be the entire window
	glViewport(0, 0, w, h);

	// Set the correct perspective.
	gluPerspective(45.0f, ratio, 0.1f, 100.0f);

	// Get Back to the Modelview
	glMatrixMode(GL_MODELVIEW);
}

int frame;
long time, timebase, timelast;
char s[50];

bool carOnGround(){
	vector<fPoint> featurePoints = car.getfeaturePoints();
	for(unsigned int i=0; i<featurePoints.size(); i++){
		if(!ground.pointOnGround(featurePoints[i]) && !ground.pointOnPark(featurePoints[i])){
			return false;
		}
	}
	return true;
}

bool carOnPark(){
	vector<fPoint> featurePoints = car.getfeaturePoints();
	for(unsigned int i=0; i<featurePoints.size(); i++){
		if(!ground.pointOnPark(featurePoints[i])){
			return false;
		}
	}
	return true;
}

void renderScene(void){
	// Clear Color and Depth Buffers
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	// FPS
	frame++;
	time = glutGet(GLUT_ELAPSED_TIME);
	if (time - timebase > 1000)
	{
		sprintf(s, "FPS:%4.2f",
				frame * 1000.0 / (time - timebase));
		timebase = time;
		frame = 0;
	}
	float timedelta = (time - timelast) / 1000.0f;
	timelast = time;

	// Update Car
	car.Step(
		inputManagerY.getValue(timedelta),
		inputManagerX.getValue(timedelta),
		timedelta
	);

	if(!carOnGround())car.reset();

	// Reset transformations
	glLoadIdentity();

	// Set the camera
	vector<float> front = car.forwardVector();
	cameraX = cameraX * 0.99 + (car.position[0] - front[0] * 10.0f) * 0.01;
	cameraZ = cameraZ * 0.99 + (car.position[2] - front[2] * 10.0f) * 0.01;
	gluLookAt(cameraX, 10.0f, cameraZ,
			  car.position[0], 1.0f, car.position[2],
			  0.0f, 1.0f, 0.0f);

	// Draw ground
	glColor3f(0.9f, 0.9f, 0.9f);
	glBegin(GL_QUADS);
	glVertex3f(-100.0f, -0.1f, -100.0f);
	glVertex3f(-100.0f, -0.1f, 100.0f);
	glVertex3f(100.0f, -0.1f, 100.0f);
	glVertex3f(100.0f, -0.1f, -100.0f);
	glEnd();

	ground.Draw();
	car.Draw();

	glColor3f(0.0f, 1.0f, 1.0f);

	glPushMatrix();
	glLoadIdentity();
	setOrthographicProjection();
	renderBitmapString(5, 30, 0, GLUT_BITMAP_HELVETICA_18, s);
	glPopMatrix();
	restorePerspectiveProjection();

	glutSwapBuffers();
}

void processNormalKeys(unsigned char key, int xx, int yy){
	if (key == 27)
		exit(0);
}

void pressKey(int key, int xx, int yy)
{
	switch (key)
	{
	case GLUT_KEY_UP:
		inputManagerY.rawValue = 1.0f;
		break;
	case GLUT_KEY_DOWN:
		inputManagerY.rawValue = -1.0f;
		break;
	case GLUT_KEY_LEFT:
		inputManagerX.rawValue = 1.0f;
		break;
	case GLUT_KEY_RIGHT:
		inputManagerX.rawValue = -1.0f;
		break;
	}
}
void pressNormalKey(unsigned char key, int xx, int yy)
{
	switch (key)
	{
	case 'w':
		inputManagerY.rawValue = 1.0f;
		break;
	case 's':
		inputManagerY.rawValue = -1.0f;
		break;
	case 'a':
		inputManagerX.rawValue = 1.0f;
		break;
	case 'd':
		inputManagerX.rawValue = -1.0f;
		break;
	}
}

void releaseKey(int key, int x, int y)
{
	switch (key)
	{
	case GLUT_KEY_RIGHT:
	case GLUT_KEY_LEFT:
		inputManagerX.rawValue = 0;
		break;
	case GLUT_KEY_UP:
	case GLUT_KEY_DOWN:
		inputManagerY.rawValue = 0;
		break;
	}
}

void releaseNormalKey(unsigned char key, int x, int y)
{
	switch (key)
	{
	case 'd':
	case 'a':
		inputManagerX.rawValue = 0;
		break;
	case 'w':
	case 's':
		inputManagerY.rawValue = 0;
		break;
	}
}

int main(int argc, char **argv){
	// init GLUT and create window
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA | GLUT_MULTISAMPLE);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(480, 320);
	glutCreateWindow("GL Playground");

	// register callbacks
	glutDisplayFunc(renderScene);
	glutReshapeFunc(changeSize);
	glutIdleFunc(renderScene);

	glutIgnoreKeyRepeat(1);
	glutKeyboardFunc(processNormalKeys);
	glutSpecialFunc(pressKey);
	glutKeyboardFunc(pressNormalKey);
	glutSpecialUpFunc(releaseKey);
	glutKeyboardUpFunc(releaseNormalKey);

	// OpenGL init
	glEnable(GL_DEPTH_TEST);

	// enter GLUT event processing cycle
	glutMainLoop();

	return 1;
}
